﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Reflection;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Security.Cryptography;

namespace CloudUpload
{
    public class TaskInfoFiles
    {
        public Dictionary<string, string> files = null;
        public string targetpath { get; set; }
        public string account { get; set; }
        public string container { get; set; }
        public string signature { get; set; }
        public bool usehttps { get; set; }
        public Control ctrl { get; set; }
        public Control button { get; set; }
    }

    public class AccountInfo
    {
        public string account { get; set; }
        public string container { get; set; }
        public string signature { get; set; }
        public bool usehttps { get; set; }
    }

    public class FileUpInfo
    {
        public string endpoint { get; set; }
        public string signature { get; set; }
        public string container { get; set; }
        public string filesourcepath { get; set; }
        public string target { get; set; }
        public CountdownEvent endEvent;
        public ConcurrentQueue<double> speeds;
        public int totFiles;
        public Control txtbox;
    }

    public class FileDownInfo
    {
        public string endpoint { get; set; }
        public string signature { get; set; }
        public string container { get; set; }
        public Dictionary<string, string> files = null;
        public Control button { get; set; }
        public Control ctrl { get; set; }
    }

    public class FileDeleteInfo
    {
        public string endpoint { get; set; }
        public string signature { get; set; }
        public string container { get; set; }
        public List<string> files = null;
        public Control button { get; set; }
        public Control ctrl { get; set; }
    }

    public static class Helper
    {
        public static string EncryptString(string Message, string Passphrase = "Cloud Upload Secret")
        {
            byte[] Results;
            System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

            // Step 1. We hash the passphrase using MD5
            // We use the MD5 hash generator as the result is a 128 bit byte array
            // which is a valid length for the TripleDES encoder we use below

            MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
            byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));

            // Step 2. Create a new TripleDESCryptoServiceProvider object
            TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

            // Step 3. Setup the encoder
            TDESAlgorithm.Key = TDESKey;
            TDESAlgorithm.Mode = CipherMode.ECB;
            TDESAlgorithm.Padding = PaddingMode.PKCS7;

            // Step 4. Convert the input string to a byte[]
            byte[] DataToEncrypt = UTF8.GetBytes(Message);

            // Step 5. Attempt to encrypt the string
            try
            {
                ICryptoTransform Encryptor = TDESAlgorithm.CreateEncryptor();
                Results = Encryptor.TransformFinalBlock(DataToEncrypt, 0, DataToEncrypt.Length);
            }
            finally
            {
                // Clear the TripleDes and Hashprovider services of any sensitive information
                TDESAlgorithm.Clear();
                HashProvider.Clear();
            }

            // Step 6. Return the encrypted string as a base64 encoded string
            return Convert.ToBase64String(Results);
        }

        public static string DecryptString(string Message, string Passphrase = "Cloud Upload Secret")
        {
            if (string.IsNullOrEmpty(Message))
                return "";

            byte[] Results;
            System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

            // Step 1. We hash the passphrase using MD5
            // We use the MD5 hash generator as the result is a 128 bit byte array
            // which is a valid length for the TripleDES encoder we use below

            MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
            byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));

            // Step 2. Create a new TripleDESCryptoServiceProvider object
            TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

            // Step 3. Setup the decoder
            TDESAlgorithm.Key = TDESKey;
            TDESAlgorithm.Mode = CipherMode.ECB;
            TDESAlgorithm.Padding = PaddingMode.PKCS7;

            // Step 4. Convert the input string to a byte[]
            byte[] DataToDecrypt = Convert.FromBase64String(Message);

            // Step 5. Attempt to decrypt the string
            try
            {
                ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
                Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
            }
            finally
            {
                // Clear the TripleDes and Hashprovider services of any sensitive information
                TDESAlgorithm.Clear();
                HashProvider.Clear();
            }

            // Step 6. Return the decrypted string in UTF8 format
            return UTF8.GetString(Results);
        }

        private delegate void SetPropertyThreadSafeDelegate<TResult>(Control @this, Expression<Func<TResult>> property, TResult value); 
        
        public static void SetPropertyThreadSafe<TResult>(this Control @this, Expression<Func<TResult>> property, TResult value) 
        { 
            var propertyInfo = (property.Body as MemberExpression).Member as PropertyInfo; 
            if (propertyInfo == null || !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) || @this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null) 
            { 
                throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control."); 
            } 
            if (@this.InvokeRequired) 
            { 
                @this.Invoke(new SetPropertyThreadSafeDelegate<TResult>(SetPropertyThreadSafe), new object[] { @this, property, value }); 
            } 
            else 
            { 
                @this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value }); 
            } 
        }

        public static void UploadFiles(AccountInfo ai, Dictionary<string, string> Files, string TargetPath, Control Ctrl, Control Btn)
        {
            TaskInfoFiles ti = new TaskInfoFiles { files = Files, targetpath= TargetPath, signature = ai.signature, account = ai.account, container = ai.container, usehttps = ai.usehttps, ctrl = Ctrl, button = Btn };
            ThreadPool.QueueUserWorkItem(new WaitCallback(UploadFilesThread), (object)ti);
        }

        public static void UploadFilesThread(Object stateInfoFiles)
        {
            TaskInfoFiles ti = (TaskInfoFiles)stateInfoFiles;
            Dictionary<string, string> files = ti.files;
            string targetpath = ti.targetpath;
            string account = ti.account;
            string container = ti.container;
            string signature = ti.signature;
            bool usehttps = ti.usehttps;
            Control ctrl = ti.ctrl;
            Control btn = ti.button;

            ManualResetEvent[] manualEvents = new ManualResetEvent[files.Count];

            string endpoint = string.Format("http{0}://{1}.blob.core.windows.net/", (usehttps) ? "s" : "", account);

            ThreadPool.SetMaxThreads(6, 6);

            ConcurrentQueue<double> speeds = new ConcurrentQueue<double>();

            CountdownEvent endevent = new CountdownEvent(files.Count);

            ctrl.SetPropertyThreadSafe(() => ctrl.Text, string.Format("Sending {0} file(s)...", files.Count));

            DateTime startdt = DateTime.Now;
            int curfile = 0;
            foreach (var source in files)
            {
                ManualResetEvent evt = new ManualResetEvent(false);
                manualEvents[curfile] = evt;

                FileUpInfo fui = new FileUpInfo { container = container, endpoint = endpoint, signature = signature, 
                    filesourcepath = source.Key, 
                    target = string.Format("{0}{1}", targetpath + source.Value, Path.GetFileName(source.Key)),
                    endEvent = endevent, speeds = speeds, totFiles = files.Count, txtbox = ctrl};
                ThreadPool.QueueUserWorkItem(new WaitCallback(UploadFileTask), (object)fui);
                curfile++;
            }

            endevent.Wait();
            TimeSpan ts2 = DateTime.Now - startdt;
            double speedAvg = speeds.Average();

            string msg = string.Format("Sent {0} file(s) in {1:0.00} seconds with average speed {2:0.00} ko/s", files.Count, ts2.TotalSeconds, speedAvg);
            ctrl.SetPropertyThreadSafe(() => ctrl.Text, msg);

            btn.SetPropertyThreadSafe(() => btn.Enabled, true);
        }

        public static void UploadFileTask(Object stateInfo)
        {
            FileUpInfo ti = (FileUpInfo)stateInfo;
            string file = ti.filesourcepath;
            string targetpath = ti.target;
            string endpoint = ti.endpoint;
            string container = ti.container;
            string signature = ti.signature;

            DateTime startdt = DateTime.Now;

            BlobHelper bh = new BlobHelper(endpoint, signature);
            long totsend = 0;

            using (FileStream fs = new FileStream(file, FileMode.Open))
            {
                totsend = fs.Length;
                if (totsend > 0x3FFFFFF)
                {
                    int tailleblock = 0x80000;
                    long nbblocks = (totsend / tailleblock) + 1;
                    string[] blockIds = new string[nbblocks];
                    byte[] buffer = new byte[tailleblock];

                    int currentblock = 0;
                    int num;
                    while ((num = fs.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        if (!bh.PutBlock(container, targetpath, currentblock, blockIds, buffer))
                        {
                            ti.endEvent.Signal();
                            return;
                        }
                        currentblock++;
                    }
                    bh.PutBlockList(container, targetpath, blockIds);
                }
                else
                {
                    bh.PutBlob(container, targetpath, fs);
                }
            }

            TimeSpan ts2 = DateTime.Now - startdt;
            long enko = totsend / 1024;
            double vitesse = (totsend / ts2.TotalSeconds) / 1024;

            ti.speeds.Enqueue(vitesse);

            int remainingCount = ti.endEvent.CurrentCount - 1;
            string msg = string.Format("Still to upload {0}/{1} files", remainingCount, ti.totFiles);
            ti.txtbox.SetPropertyThreadSafe(() => ti.txtbox.Text, msg);

            ti.endEvent.Signal();
        }

        public static bool UnitUploadFile(string Endpoint, string Container, string Signature, string SourceFile, string Target)
        {
            string file = SourceFile;
            string targetpath = Target;
            string endpoint = Endpoint;
            string container = Container;
            string signature = Signature;

            BlobHelper bh = new BlobHelper(endpoint, signature);
            long totsend = 0;

            using (FileStream fs = new FileStream(file, FileMode.Open))
            {
                totsend = fs.Length;
                if (totsend > 0x3FFFFFF)
                {
                    int tailleblock = 0x80000;
                    long nbblocks = (totsend / tailleblock) + 1;
                    string[] blockIds = new string[nbblocks];
                    byte[] buffer = new byte[tailleblock];

                    int currentblock = 0;
                    int num;
                    while ((num = fs.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        if (!bh.PutBlock(container, targetpath, currentblock, blockIds, buffer))
                        {
                            return false;
                        }
                        currentblock++;
                    }
                    if (!bh.PutBlockList(container, targetpath, blockIds))
                    {
                        return false;
                    }
                }
                else
                {
                    if (!bh.PutBlob(container, targetpath, fs))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        public static List<string> GetDirList(AccountInfo ai, string Dir)
        {
            string endpoint = string.Format("http{0}://{1}.blob.core.windows.net/", (ai.usehttps) ? "s" : "", ai.account);
            BlobHelper bh = new BlobHelper(endpoint, ai.signature);
            return bh.ListBlobs(ai.container, Dir, ListBlobOptions.Folders);
        }

        public static List<string> GetFilesList(AccountInfo ai, string Dir)
        {
            string endpoint = string.Format("http{0}://{1}.blob.core.windows.net/", (ai.usehttps) ? "s" : "", ai.account);
            BlobHelper bh = new BlobHelper(endpoint, ai.signature);
            return bh.ListBlobs(ai.container, Dir, ListBlobOptions.Blobs);
        }

        public static void DownloadFiles(AccountInfo ai, Dictionary<string, string> Files, Control Ctrl, Control Btn)
        {
            FileDownInfo fdi = new FileDownInfo { container = ai.container, signature= ai.signature, 
                endpoint = string.Format("http{0}://{1}.blob.core.windows.net/", (ai.usehttps) ? "s" : "", ai.account), 
                files = Files, ctrl = Ctrl, button = Btn  };
            ThreadPool.QueueUserWorkItem(new WaitCallback(DownloadFiles), (object)fdi);
        }

        public static void DownloadFiles(Object stateInfo)
        {
            FileDownInfo fdi = (FileDownInfo)stateInfo;

            BlobHelper bh = new BlobHelper(fdi.endpoint, fdi.signature);

            foreach (var job in fdi.files)
            {
                string dir = Path.GetDirectoryName(job.Value);

                string name = job.Key;
                name = name.Substring(name.LastIndexOf('/') + 1);
                string msg = string.Format("Downloading file: {0}", name);
                fdi.ctrl.SetPropertyThreadSafe(() => fdi.ctrl.Text, msg);

                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }

                using (FileStream fs = new FileStream(job.Value, FileMode.Create))
                {
                    bh.GetBlob(fdi.container,job.Key, fs);
                }
            }

            string msg2 = string.Format("Finished downloading {0} file(s).", fdi.files.Count);
            fdi.ctrl.SetPropertyThreadSafe(() => fdi.ctrl.Text, msg2);
            fdi.button.SetPropertyThreadSafe(() => fdi.button.Enabled, true);
        }

        public static bool DeleteFiles(AccountInfo ai, List<string> Files, Control Ctrl, Control Btn)
        {
            FileDeleteInfo fdi = new FileDeleteInfo
            {
                container = ai.container,
                signature = ai.signature,
                endpoint = string.Format("http{0}://{1}.blob.core.windows.net/", (ai.usehttps) ? "s" : "", ai.account),
                files = Files,
                ctrl = Ctrl,
                button = Btn
            };
            ThreadPool.QueueUserWorkItem(new WaitCallback(DeleteFiles), (object)fdi);

            return true;
        }

        public static void DeleteFiles(Object stateInfo)
        {
            FileDeleteInfo fdi = (FileDeleteInfo)stateInfo;

            BlobHelper bh = new BlobHelper(fdi.endpoint, fdi.signature);

            foreach (var job in fdi.files)
            {
                string name = job;
                name = name.Substring(name.LastIndexOf('/') + 1);
                string msg = string.Format("Deleting file: {0}", name);
                fdi.ctrl.SetPropertyThreadSafe(() => fdi.ctrl.Text, msg);
                bh.DeleteBlob(fdi.container,job);
            }

            string msg2 = string.Format("Finished deleting {0} file(s).", fdi.files.Count);
            fdi.ctrl.SetPropertyThreadSafe(() => fdi.ctrl.Text, msg2);

            fdi.button.SetPropertyThreadSafe(() => fdi.button.Enabled, true);
        }
  
    }
}
